package org.einbazen.xmlliteratuur;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Stack;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import com.csvreader.CsvReader;
import com.csvreader.CsvWriter;
import com.esotericsoftware.yamlbeans.YamlReader;
import com.esotericsoftware.yamlbeans.YamlWriter;
import com.google.gson.Gson;

public class Student {
	public String studentnummer;
	public String naam;
	public String adres;
	public String postcode;
	public String woonplaats;
	public String geslacht;
	public String telefoonnummer;
	public String inschrijvingsdatum;

	public Student(){
		this.studentnummer = "";
		this.naam = "";
		this.adres = "";
		this.postcode = "";
		this.woonplaats = "";
		this.geslacht = "";
		this.telefoonnummer = "";
		this.inschrijvingsdatum = "";
	}

	public Student(String studentnummer, String naam, String adres, String postcode, String woonplaats, String geslacht, String telefoonnummer, String inschrijvingsdatum){
		this.studentnummer = studentnummer;
		this.naam = naam;
		this.adres = adres;
		this.postcode = postcode;
		this.woonplaats = woonplaats;
		this.geslacht = geslacht;
		this.telefoonnummer = telefoonnummer;
		this.inschrijvingsdatum = inschrijvingsdatum;
	}

	private class StudentHandler extends DefaultHandler {
		private ArrayList<Student> students;
		private Stack<String> elementDepth = new Stack<String>();

		public StudentHandler() {
			this.students = new ArrayList<Student>();
		}

		public ArrayList<Student> getStudents() {
			return students;
		}

		@Override
		public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
			//TODO: break out here when we processed enough

			//Pop the current element
			elementDepth.pop();
		}

		@Override
		public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
			//Check if we can parse a student
			if(elementDepth.size() == 2 && elementDepth.peek().equalsIgnoreCase("studenten")) {
				students.add(new Student(
						atts.getValue("id"), 
						atts.getValue("naam"), 
						atts.getValue("adres"), 
						atts.getValue("postcode"), 
						atts.getValue("woonplaats"), 
						atts.getValue("geslacht"), 
						atts.getValue("telefoonnr"),
						atts.getValue("inschrijvingsdatum")
						));
			}

			//Add current name to the elementDepth stack
			elementDepth.add(localName);
		}
	}
	
	private class StudentWriter extends XMLWriter {
		private Stack<String> elementDepth = new Stack<String>();
		private Student student;
		private boolean modify;
		private boolean modified = false;
		
		public StudentWriter(XMLReader parent, Writer writer, Student student, boolean modify) {
			super(parent, writer);
			
			this.student = student;
			this.modify = modify;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
			//Check if we modify the student
			if(modify && elementDepth.size() == 2 && elementDepth.peek().equalsIgnoreCase("studenten") && atts.getValue("id").equalsIgnoreCase(student.studentnummer)) {
				atts = createAttributes();
				modified = true;
			}
			
			//Check if we add the student and there is a student with the same id
			if(!modify && elementDepth.size() == 2 && elementDepth.peek().equalsIgnoreCase("studenten") && atts.getValue("id").equalsIgnoreCase(student.studentnummer)) {
//				throw new RuntimeException("Duplicate student with id: " + student.studentnummer);
			}
			
			//Add current name to the elementDepth stack
			elementDepth.add(localName);
			
			//Write the start element
			super.startElement(uri, localName, qName, atts);
		}

		@Override
		public void endElement(String uri, String localName, String qName) throws SAXException {
			//Check if we add the student
			if(!modify && elementDepth.size() == 2 && elementDepth.peek().equalsIgnoreCase("studenten") && localName.equalsIgnoreCase("studenten")) {
				//Create attributes
				Attributes atts = createAttributes();
				
				//Create student
				super.startElement("", "student", "student", atts);
				super.endElement("", "student", "student");
			}
			
			//Check if we modify the student
			if(modify && !modified && elementDepth.size() == 2 && elementDepth.peek().equalsIgnoreCase("studenten") && localName.equalsIgnoreCase("studenten")) {
				throw new RuntimeException("Couldn't edit unknown student with id: " + student.studentnummer);
			}
			
			//Pop the current element
			elementDepth.pop();
			
			//Write the endElement
			super.endElement(uri, localName, qName);
		}
		
		private Attributes createAttributes() {
			AttributesImpl att = new AttributesImpl();
			att.addAttribute("", "adres", "adres", "CDATA", student.adres);
			att.addAttribute("", "geslacht", "geslacht", "CDATA", student.geslacht);
			att.addAttribute("", "id", "id", "CDATA", student.studentnummer);
			att.addAttribute("", "inschrijvingsdatum", "inschrijvingsdatum", "CDATA", student.inschrijvingsdatum);
			att.addAttribute("", "naam", "naam", "CDATA", student.naam);
			att.addAttribute("", "postcode", "postcode", "CDATA", student.postcode);
			att.addAttribute("", "telefoonnr", "telefoonnr", "CDATA", student.telefoonnummer);
			att.addAttribute("", "woonplaats", "woonplaats", "CDATA", student.woonplaats);
			
			return att;
		}
	}

	public ArrayList<Student> FromXML(String xmlFile) {
		File file = new File(xmlFile);
		try {
			if(!file.exists())
				file.createNewFile();
		} catch(IOException e) {
			throw new RuntimeException("Error creating new xml file", e);
		}
		
		XMLReader xmlReader;
		try {
			xmlReader = XMLReaderFactory.createXMLReader();
		} catch(SAXException se) {
			throw new RuntimeException("Error while creating xml readerer", se);
		}
		
		StudentHandler handler = new StudentHandler();
		try {
			synchronized(xmlReader) {
				xmlReader.setContentHandler(handler);
				xmlReader.parse(new InputSource(new FileInputStream(file)));
			}
		} catch(SAXException e) {
			throw new RuntimeException("Error loading student range", e);
		} catch(IOException e) {
			throw new RuntimeException("Error reading xml file", e);
		}
		
		return handler.getStudents();
	}

	public void ToXml(){
		try {
			//Create tmp file
			File tmp = File.createTempFile("xl_", ".xml");
			
			XMLReader xmlReader;
			try {
				xmlReader = XMLReaderFactory.createXMLReader();
			} catch(SAXException se) {
				throw new RuntimeException("Error while creating xml readerer", se);
			}
			
			
			//Write the student to the tmp file
			XMLWriter writer = new StudentWriter(xmlReader, new OutputStreamWriter(new FileOutputStream(tmp)), this, false);
			writer.parse(new InputSource(new FileInputStream("file.xml")));
			writer.flush();
			
			//Copy tmp file into default file
			InputStream fis = new FileInputStream(tmp);
			OutputStream fos = new FileOutputStream("file.xml");
			
			byte[] buffer = new byte[16384];
			int read;
			while( (read = fis.read(buffer)) != -1 ) {
				fos.write(buffer, 0, read);
			}
			fis.close();
			fos.flush();
			fos.close();
			
			
		} catch(SAXException e) {
			throw new RuntimeException("Error writing student", e);
		} catch(IOException e) {
			throw new RuntimeException("Error writing xml file", e);
		}
	}

	public void ToCSV()
	{
		String outputFile = "File.csv";
		boolean alreadyExists = new File(outputFile).exists();
		try {
			// use FileWriter constructor that specifies open for appending
			CsvWriter csvOutput = new CsvWriter(new FileWriter(outputFile, true), ',');

			// if the file didn't already exist then we need to write out the header line
			if (!alreadyExists)
			{
				csvOutput.write("id");
				csvOutput.write("naam");
				csvOutput.write("adres");
				csvOutput.write("postc");
				csvOutput.write("woonp");
				csvOutput.write("geslacht");
				csvOutput.write("telnr");
				csvOutput.write("inschrijfdtm");
				csvOutput.endRecord();
			}

			// write out a few records
			csvOutput.write(studentnummer);
			csvOutput.write(naam);
			csvOutput.write(adres);
			csvOutput.write(postcode);
			csvOutput.write(woonplaats);
			csvOutput.write(geslacht);
			csvOutput.write(telefoonnummer);
			csvOutput.write(inschrijvingsdatum);
			csvOutput.endRecord();

			csvOutput.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static ArrayList<Student> FromCSV(String csvFile)
	{
		ArrayList<Student> returnlist = new ArrayList<Student>();
		try {

			CsvReader students = new CsvReader(csvFile);

			students.readHeaders();

			while (students.readRecord())
			{
				String id = students.get("id");
				String naam = students.get("naam");
				String adres = students.get("adres");
				String postc = students.get("postc");
				String woon = students.get("woonp");
				String gesl = students.get("geslacht");
				String telnr = students.get("telnr");
				String inschrijfdtm = students.get("inschrijfdtm");
				Student student = new Student(id, naam, adres, postc, woon, gesl, telnr, inschrijfdtm);

				returnlist.add(student);
			}		
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return returnlist;
	}

	public void ToJSON()
	{
		//We need to add to the JSON-objectlist so first deserialize!
		ArrayList<Student> list = FromJSON("File.json");
		list.add(this);
		Gson gson = new Gson();
		String json = gson.toJson(list);
		BufferedWriter bw = null;
		try
		{
			bw = new BufferedWriter(new FileWriter("File.json"));
			bw.write(json);
			bw.flush();
			bw.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static ArrayList<Student> FromJSON(String jsonFile)
	{
		ArrayList<Student> returnlist = new ArrayList<Student>(); 
		if (!new File(jsonFile).exists())
			return returnlist;

		try
		{
			Gson gson = new Gson();
			BufferedReader br = new BufferedReader(new FileReader(jsonFile));
			String json = "";
			String curline = br.readLine();
			while (curline != null)
			{
				json += curline;
				curline = br.readLine();
			}

			returnlist = gson.fromJson(json, ArrayList.class);
			br.close();
		}
		catch (Exception e)
		{
		}
		return returnlist;
	}

	public void ToYAML()
	{
		ArrayList<Student> students = Student.FromYAML("File.yml");
		students.add(this);
		try
		{
			YamlWriter yaml = new YamlWriter(new FileWriter("File.yml"));
			yaml.write(students);
			yaml.close();
		}
		catch (Exception e)
		{
			System.out.println(e);
		}
	}

	public static ArrayList<Student> FromYAML(String yamlFile)
	{
		ArrayList<Student> returnlist = new ArrayList<Student>();
		if (!new File(yamlFile).exists())
			return returnlist;

		try
		{
			YamlReader yr = new YamlReader(new FileReader(yamlFile));
			returnlist = (ArrayList<Student>)yr.read();
		}
		catch (Exception e)
		{
			System.out.println(e);
		}
		return returnlist;
	}

}